package main

import (
	"bufio"
	"gitee.com/jmdvirus/jkgo/jk/jklog"
	"io"
	"net"
	"strconv"
	"strings"
	"time"
)

type NetManager struct {
	listener *net.TCPListener
	apMng    *AppManager
}

func (m *NetManager) notifyAll(data []byte, conn net.Conn) error {
	apps := m.apMng.AllApps()
	jklog.L().Debugln("start to notify")
	for i, k := range *apps {
		jklog.L().Debugln("is self (?) ", k.Net.RemoteAddr().String(), " args ", conn.RemoteAddr().String())
		if k.IsSelf(conn) {
			continue
		}
		jklog.L().Debugln("notify index ", i)
		n, err := k.Net.Write(data)
		if err != nil {
			jklog.L().Errorln("send to ", k.Name, " fail ", err)
		} else {
			jklog.L().Infoln("send to ", k.Name, " success length ", n)
		}
	}
	return nil
}

func (m *NetManager) doRegister(data string, conn net.Conn) error {
	appone := AppInfo{Name: data, Net: conn}
	n, _ := m.apMng.AppManagerAdd(appone)
	jklog.L().Infoln("apps have ", n, " name = ", data)
	return nil
}

func (m *NetManager) doClip(data []byte, conn net.Conn) error {
	jklog.L().Infoln("notify to others ", conn.RemoteAddr().String(), " of len ", len(data))
	return m.notifyAll(data, conn)
}

func (m *NetManager) parseData(data string, conn net.Conn) error {
	jklog.L().Infoln("data = ", data)
	mp, err := MsgParserNew(data)
	if err != nil {
		return err
	}
	switch mp.MsgParserCmd() {
	case "register":
		appone := AppInfo{Name: mp.MsgParserData(), Net: conn}
		n, _ := m.apMng.AppManagerAdd(appone)
		jklog.L().Infoln("apps have ", n, " name = ", mp.MsgParserData())
	case "clip":
		jklog.L().Infoln("notify to others ", conn.RemoteAddr().String())
		m.notifyAll([]byte(data), conn)
	default:
		jklog.L().Warnln("unknown cmd ", mp.MsgParserCmd())
	}
	return nil
}

func (m *NetManager) handler(conn net.Conn) {
	//defer conn.Close()
	bufferReader := bufio.NewReader(conn)
	for {
		recvByte, err := bufferReader.ReadString('|')
		if err != nil {
			if err == io.EOF {
				jklog.L().Errorln("client closed")
				m.apMng.AppManagerDelConn(conn)
				break
			}
			jklog.L().Errorln(err)
			time.Sleep(time.Second * 5)
			continue
		}
		recvByte = strings.TrimRight(recvByte, "|")
		if recvByte == "register" {
			recvByte, err := bufferReader.ReadString('\n')
			if err != nil {
				jklog.L().Errorln(err)
				continue
			}
			strings.TrimRight(recvByte, "\n")
			err = m.doRegister(recvByte, conn)
			if err != nil {
				jklog.L().Errorln(err)
			}
		} else if recvByte == "clip" {
			recvByte, err := bufferReader.ReadString('|')
			if err != nil {
				jklog.L().Errorln(err)
				continue
			}
			recvByte = strings.TrimRight(recvByte, "|")
			jklog.L().Debugln("clip length string = ", recvByte)
			n, err := strconv.Atoi(recvByte)
			if err != nil {
				jklog.L().Errorln(err, " of string ", recvByte)
				n = 102400
			}
			jklog.L().Debugln("clip length value = ", n)
			recvData := make([]byte, n+1)
			maxlen := n
			curlen := 0
			startSend := "startSend|" + strconv.Itoa(n) + "|"
			m.doClip([]byte(startSend), conn)
			for {
				n, err = bufferReader.Read(recvData)
				if err != nil {
					jklog.L().Errorln("read failed: ", err)
					break
				}
				curlen += n

				err = m.doClip(recvData[:n], conn)
				if err != nil {
					jklog.L().Errorln(err)
				}
				if curlen >= maxlen {
					break
				}
			}
			conn.Write([]byte("endSend|"))

		} else {
			n := len(recvByte)
			if n > 20 {
				n = 20
			}
			jklog.L().Errorln("invalid data ", recvByte[:n])
		}
	}
}

func StartNet(addr string, port int) (*NetManager, error) {
	nm := &NetManager{}
	nm.apMng, _ = AppManagerNew()
	fulladdr := addr + ":" + strconv.Itoa(port)
	jklog.L().Infoln("listen to ", fulladdr)
	raddr, err := net.ResolveTCPAddr("tcp", fulladdr)
	if err != nil {
		return nil, err
	}
	nm.listener, err = net.ListenTCP("tcp", raddr)
	if err != nil {
		return nil, err
	}

	defer nm.listener.Close()

	for {
		jklog.L().Infoln("start to accept")
		conn, err := nm.listener.Accept()
		if err != nil {
			jklog.L().Errorln("accept fail ", err)
			time.Sleep(time.Second * 5)
			continue
		}
		jklog.L().Infoln("accept client ", conn.RemoteAddr().String())
		go nm.handler(conn)
	}

	return nm, nil
}
